/*
 * ============================================================================
 *
 *       Filename:  main.c
 *
 *    Description:  
 *
 *        Version:  1.0
 *        Created:  09/16/19 16:37:41
 *       Revision:  none
 *       Compiler:  gcc
 *
 *         Author:  jianxi sun (jianxi), ycsunjane@gmail.com
 *   Organization:  
 *
 * ============================================================================
 */

#include <stdio.h>
#include <stdint.h>
#include <fcntl.h>
#include <string.h>
#include <sys/file.h>
#include <sys/types.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <mosquitto.h>
#include <unistd.h>
#include <signal.h>
#include <ev.h>

#include "arg.h"
#include "log.h"
#include "config.h"
#include "list.h"
#include "common.h"
#include "packet_capture.h"
#include "socket_connect.h"

zlog_category_t *z_cate = NULL; /* zlog 描述符 */

int isconnect = 0;
int get_mosq_connect_state()
{
	return isconnect;
}

void my_log_callback(struct mosquitto *mosq, void *obj, int level, const char *str)
{
        Lzlog_debug("%s", str);
	sys_debug("%s\n", str);
}

void my_connect_callback(struct mosquitto *mosq, void *obj, int result)
{
	isconnect = 1;
	sys_debug("connect callback: %d\n", result);
	if (args_info.subtopic_given) {
		mosquitto_subscribe(mosq, NULL, args_info.subtopic_arg, args_info.subqos_arg);
	}
}

void my_disconnect_callback(struct mosquitto *mosq, void *obj, int rc)
{
	isconnect = 0;
	rc = mosquitto_reconnect(mosq);
	sys_debug("mosquitto_reconnect: %d\n", rc);
}

void my_publish_callback(struct mosquitto *mosq, void *obj, int mid)
{
	sys_debug("publish callback: %d\n", mid);
}

#define CMDLEN	(2048)
void my_message_callback(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *message)
{
	if(message->payloadlen <= 0) {
		return;
	}
	sys_debug("%s %s\n", (char *)message->topic, (char *)message->payload);
	Lzlog_debug("Get message from MQTT:%s", (char *)message->payload);
	packet_capture_t *ctx = get_pkgcap_ctx();
	control_start_stop_write_file((const char *)message->payload, ctx);
}

int my_pw_callback(char *buf, int size, int rwflag, void *userdata)
{
	if(!args_info.keypassword_given)
		return 0;

	int passlen = strlen(args_info.keypassword_arg);
	if(size < passlen) {
		sys_err("key password too long: %d %d\n", passlen, size);
		exit(-1);
	}
	memcpy(buf, args_info.keypassword_arg, passlen);
	return passlen;
}

void publish_msg(struct mosquitto *mosq, void *data, int len)
{
	mosquitto_publish(mosq, NULL, args_info.topic_arg, len, data, args_info.qos_arg, 0);
}

void change_processname(char *argv[])
{
	if (args_info.process_name_given && strlen(args_info.process_name_arg)) {
		strcpy(argv[0], args_info.process_name_arg);
	}
}

void state_check_timer_cb(struct ev_loop *loop, struct ev_timer *w, int revents)
{
	return;
}

void *thread_pcap_packet_captrue(void *arg)
{
        int ret = pcap_file_write_lock_init();
        if (ret != 0) {
                if (ret != EBUSY) {
                        Lzlog_debug("Sorry, init pcap_file_write_lock filed! Code:%d\n", ret);
                        exit(-1);
                }
        }
	/* 创建网络抓包设备并开始抓包 */
	packet_capture_t *pkgcap_ctx;
	while (1) {
		struct mosquitto *mosq  = (struct mosquitto *)arg;
		pkgcap_ctx = new_packet_capture(args_info.pcap_dev_arg);
		if (!pkgcap_ctx) {
			sleep (1);
			continue;
		}
		set_single_file_max_size(args_info.pcap_size_arg); // 设置单个pcap文件的最大尺寸(MB)
		pkgcap_ctx->vlanid = args_info.vlanid_arg;
		pkgcap_ctx->write_dir = strdup(args_info.pcap_path_arg);
		INIT_LIST_HEAD(&pkgcap_ctx->files_list);
		pkgcap_ctx->files_count = 0;
		pkgcap_ctx->mosq = mosq;
		pkgcap_ctx->mqtt_publish = publish_msg;
		pcap_loop(pkgcap_ctx->handler, 0, packet_handler_cb, NULL);

		destroy_packet_capture(pkgcap_ctx);
	} /* End while (1) */
	return NULL;
}

static int one_instance()
{
	char pidfile[256];
	snprintf(pidfile, 256, "/var/run/packet_capture_%d.pid", args_info.vlanid_arg);
	int pid_file = open(pidfile, O_CREAT | O_RDWR | O_CLOEXEC, 0666);
	int rc = flock(pid_file, LOCK_EX | LOCK_NB);
	if(rc) {
		if(EWOULDBLOCK == errno) {
			sys_warn("Another mqttdiris running\n");
			exit(1);
		}
	}
	return 0;
}

int business_process()
{
	int ret = -1;
	struct mosquitto *mosq = NULL;
	mosquitto_lib_init();
	mosq = mosquitto_new(args_info.id_arg, args_info.clean_session_flag, NULL);
	if (!mosq) {
		switch(errno) {
		case ENOMEM:
			sys_err("Error: Out of memory.\n");
			Lzlog_error("mosquitto_new error!Out of memory.");
			break;
		case EINVAL:
			sys_err("Error: Invalid id.\n");
			Lzlog_error("mosquitto_new error!nvalid id.");
			break;
		}
		goto cleanup;
	}

	if (args_info.willtopic_given) {
		int payloadlen = 0;
		if (args_info.willpayload_given) {
			payloadlen = strlen(args_info.willpayload_arg);
		}
		ret = mosquitto_will_set(mosq, args_info.willtopic_arg, 
			payloadlen, args_info.willpayload_arg,
			args_info.willqos_arg,
			args_info.willretain_arg);
		if (ret != MOSQ_ERR_SUCCESS) {
			sys_err("Error: mosquitto set will failed\n");
			Lzlog_error("mosquitto set will failed.");
			exit(-1);
		}
	}

	mosquitto_log_callback_set(mosq, my_log_callback);
	mosquitto_connect_callback_set(mosq, my_connect_callback);
	mosquitto_disconnect_callback_set(mosq, my_disconnect_callback);
	mosquitto_publish_callback_set(mosq, my_publish_callback);
	mosquitto_message_callback_set(mosq, my_message_callback);

	if (args_info.username_given && args_info.password_given) {
		sys_debug("set username: %s password: %s\n", args_info.username_arg, args_info.password_arg);
		Lzlog_debug("set username: %s password: %s", args_info.username_arg, args_info.password_arg);
		if(mosquitto_username_pw_set(mosq, args_info.username_arg,
				args_info.password_arg) != MOSQ_ERR_SUCCESS) {
			sys_err("set username password failed\n");
			Lzlog_error("mosquitto set username password failed.");
			goto cleanup;
		}
	}

	if (args_info.cafile_given && args_info.cert_given && args_info.key_given) {
		mosquitto_tls_set(mosq, args_info.cafile_arg, 
			NULL, args_info.cert_arg, args_info.key_arg,
			my_pw_callback);
	}

	mosquitto_tls_insecure_set(mosq, args_info.insecure_flag);

	loop = EV_DEFAULT;
	ev_timer state_check_ev_timer;
	ev_timer_init(&state_check_ev_timer, state_check_timer_cb, 0, 10);
	ev_timer_start(loop, &state_check_ev_timer);

reconnect:
	sleep(1);
	ret = mosquitto_connect(mosq, args_info.host_arg, args_info.port_arg,
		args_info.keepalive_arg);
	if(ret != MOSQ_ERR_SUCCESS) {
		sys_err("mosquitto_connect failed: %s(%d)\n", strerror(errno), errno);
		Lzlog_error("mosquitto_connect failed: %s(%d)", strerror(errno), errno);
		goto reconnect;
	}

	sys_debug("mosquitto thread start\n");
	Lzlog_info("mosquitto thread start\n");
	ret = mosquitto_loop_start(mosq);
	if(ret != MOSQ_ERR_SUCCESS) {
		sys_err("mosquitto_loop_start failed\n");
		Lzlog_error("mosquitto_loop_start failed");
		goto cleanup;
	}
	Lzlog_info("Creat net packet capture and init it!");
	/* 创建网络抓包设备并开始抓包 */
	safe_pthread_create(thread_pcap_packet_captrue, (void *)mosq);

	ev_run(loop, 0);
	ev_timer_stop(loop, &state_check_ev_timer);
	ev_loop_destroy(loop);

cleanup:

	mosquitto_destroy(mosq);
	mosquitto_lib_cleanup();
	return 0;
}
void signal_handle_exit(int sig)
{
	return;
}
/* 获取退出的子进程的状态然后重新创建并开启子进程 */
void signal_handle_restart_business(int sig)
{
	int status;
	/* 获取退出的子进程的状态 */
	int quit_pid = wait(&status);
	Lzlog_debug("Business process %d quit, status %d, restart!",
		quit_pid, status);
	sleep(2);
	pid_t pid = fork();
	if (pid == 0) {
		business_process();
	}
	return;
}

/* 监测进程处理函数 */
void check_process()
{
	Lzlog_info("Check process Starting!");
	signal(SIGCHLD, signal_handle_restart_business);
	while(1)
		pause();
	return;
}

int main(int argc, char *argv[])
{
	proc_args(argc, argv);
	if (args_info.log_conf_given) {//初始化日志
		int rc = zlog_init(args_info.log_conf_arg);
		if (0 == rc) {
			z_cate = zlog_get_category("my_log");
		} else {
			sys_err("zlog init error!%s\n", strerror(errno));
			z_cate = NULL;
		}
	} else {
		sys_debug("Not set zlog config!\n");
	}
	change_processname(argv);//重命名自身进程名
	one_instance();
	if(daemon_mode) {
		daemon(1, 0);
	}
#if 0
	business_process();
#else
	pid_t pid = fork();
	if (pid == -1) {
		Lzlog_error("fork failed!");
		return -1;
	}
	if  (pid == 0) {
		business_process();
	} else {
		check_process();
	}
#endif
	return 0;
}


#if 0
/* valgrind TEST */
sudo valgrind --leak-check=full ./packet_capture --pcap_dev="ens33" --vlanid="4200" --host="192.168.10.181" --topic="/pkgcap/4200/ToManager" --subtopic="/pkgcap/4200/ToCapture" -d 7 --username 'admin' --password 'innobank188*' --pcap_size="6" --log_conf="/etc/packet_capture_log.cfg"
#endif
